<?php

Raise::load('core.RaiseObject');
Raise::load('core.collection.RaiseCollection');
Raise::load('core.reflection.RaiseVariable');
Raise::load('core.RaiseTypedCollection');
Raise::load('db.core.RaiseDbConnection');
Raise::load('db.core.RaiseDbCommand');
Raise::load('db.relationship.RaiseDbRelationship');
Raise::load('db.relationship.RaiseDbManyToMany');
Raise::load('db.relationship.RaiseDbHasMany');
Raise::load('db.relationship.RaiseDbHasOne');
Raise::load('db.relationship.RaiseDbBelongsToOne');
Raise::load('db.sql.RaiseDbSqlIdentifier');
Raise::load('db.sql.RaiseDbSqlSelect');
Raise::load('db.sql.RaiseDbSqlQueryCondition');

/**
 * RaiseDbModel abstract class
 * defines a Database-access object
 *
 * @todo updateAll
 * @author Sam-Mauris Yong / hellclanner at live dot com
 * @license http://www.opensource.org/licenses/bsd-license New BSD License
 * @abstract
 * @package phpraise.db.core
 * @since 1.2
 */
abstract class RaiseDbModel extends RaiseObject {
    /**
     * Fetch only the current model
     */
    const LOAD_SHALLOW = 1;

    /**
     * Fetch all models defined in the relationships
     */
    const LOAD_NORMAL = 1;

    /**
     * Fetch all models defined in the relationships,
     * and all models defined in the models' relationships
     */
    const LOAD_DEEP = 2;

    /**
     * Define how much the fetch method will fetch from the database
     * @var integer
     */
    protected $loadLevel = self::LOAD_NORMAL;

    /**
     * The name of the Relation in the Database that this Model is stored to
     * If this is not provided, the class name of this model will be used instead.
     * @var string
     */
    protected $table;

    /**
     * The primary key(s) of this model
     * @var string|RaiseCollection|array
     */
    protected $primaryKey;

    /**
     * Relationship of other models to this model
     * @var RaiseTypedCollection
     */
    protected $relationships;

    /**
     * The connection that is assosciated with
     * @var RaiseDbConnection
     */
    protected $connection;

    /**
     * The default order for all fetch operations
     * @var array|RaiseCollection|RaiseDbSqlOrder
     */
    protected $order;

    /**
     * The table model
     * @var RaiseTypedCollection
     */
    private static $tableModel;

    private static $dateTimeCreatedColumn = 'DateTimeCreated';

    private static $dateTimeModifiedColumn = 'DateTimeModified';

    /**
     * Create a new RaiseDbModel
     * @param RaiseDbConnection $c The connection to the database
     */
    public function __construct($c) {
        $this->connection($c);
        $this->relationships = new RaiseTypedCollection('RaiseDbRelationship');
        if (!$this->primaryKey) {
            $this->primaryKey = $this->table() . 'Id';
        }
        self::loadModelFromDb();
        $this->initialize();
    }

    /**
     * Internal initialization of the model
     * Declared to allow overriding.
     * Add relationships, modify primary key and other settings for the model in the overriden method
     */
    protected function initialize() {

    }

    /**
     * Load the table model from the database
     */
    private function loadModelFromDb() {
        if (!self::$tableModel) {
            self::$tableModel = new RaiseTypedCollection('RaiseTypedCollection');
        }
        if (self::$tableModel->get($this->connection->hashCode() . $this->table()) == null) {
            self::$tableModel->add($this->connection->hashCode() . $this->table(), $this->connection->adapter()->tableColumns($this->table()));
        }
    }

    /**
     * Get the name of the database table of this model
     * @return string
     */
    public function table() {
        return $this->table ? $this->table : get_class($this);
    }

    /**
     * Check whether this model is using a composite primary key
     * @return boolean TRUE if the primary key is composite, and FALSE otherwise.
     */
    public function isCompositePrimaryKey() {
        return ($this->primaryKey instanceof RaiseCollection) || is_array($this->primaryKey);
    }
    
    /**
     * Get the primary key
     * @return mixed 
     */
    public function primaryKey(){
        return $this->primaryKey;
    }

    private function prepareModel($model){
        Raise::load('db.RaiseDbModelBuilder');
        if($model instanceof RaiseDbModel){
            return $model;
        }
        if (is_string($model)) {
            if (class_exists($model)) {
                $model = new $model($this->connection);
            } else {
                $model = new RaiseDbModelBuilder($this->connection, $model);
            }
        }
        return $model;
    }

    /**
     * Fetch a record using the primary key in the current model and load it into a current model object
     * @return RaiseCollection
     */
    public function fetch() {
        $params = new RaiseCollection();
        $cond = new RaiseDbSqlQueryCondition();
        $cond->where(new RaiseDbSqlWhere());
        $cond->where()->operator('AND');
        $cond->limit(1);

        foreach ($this->pkFieldList() as $pk) {
            if (property_exists($this, $pk)) {
                $name = RaiseDbSqlIdentifier::tableField($this->table(), $pk, $this->connection);
                $cond->where()->subConditions()->add(new RaiseDbSqlWhere('=', $name, '@' . $this->table() . $pk));
                $params->add('@' . $this->table() . $pk, $this->$pk);
            }
        }

        if ($this->loadLevel == self::LOAD_NORMAL || $this->loadLevel == self::LOAD_DEEP) {
            foreach ($this->relationships as $k => $r) {
                if ($r instanceof RaiseDbHasOne) {
                    $model = $this->prepareModel($r->model());

                    if ($model instanceof RaiseDbModel) {
                        $join = new RaiseDbSqlJoin($model);

                        $alias = null;
                        if (is_string($k)) {
                            $alias = RaiseDbSqlIdentifier::field($k, $this->connection)->__toString();
                            $join->alias($alias);
                        }else{
                            $alias = RaiseDbSqlIdentifier::field($model instanceof RaiseDbModelBuilder ? $model->table() : get_class($model), $this->connection)->__toString();
                            $join->alias($alias);
                        }

                        $join->type('LEFT');

                        $join->condition()->operator('AND');

                        $fKey = $r->foreignKey();
                        if ($fKey) {
                            if (!RaiseVariable::isType($fKey, array('RaiseCollection'))) {
                                $fKey = new RaiseCollection($fKey);
                            }
                        } else {
                            $fKey = $this->pkFieldList();
                        }

                        foreach ($this->pkFieldList() as $i => $pk) {
                            $id = RaiseDbSqlIdentifier::tableField($alias ? $k : $model->table(), $fKey->get($i), $this->connection);
                            $id2 = RaiseDbSqlIdentifier::tableField($this->table(), $pk, $this->connection);
                            $join->condition()->subConditions()->add(new RaiseDbSqlWhere('=', $id->__toString(), $id2->__toString()));
                        }

                        $cond->join()->add($join);
                    }
                } else if ($r instanceof RaiseDbBelongsToOne) {
                    $model = $this->prepareModel($r->model());

                    if ($model instanceof RaiseDbModel) {
                        $join = new RaiseDbSqlJoin($model);

                        $alias = null;
                        if (is_string($k)) {
                            $alias = RaiseDbSqlIdentifier::field($k, $this->connection);
                        }else{
                            $alias = RaiseDbSqlIdentifier::field($model instanceof RaiseDbModelBuilder ? $model->table() : get_class($model), $this->connection);
                        }
                        $join->alias($alias);

                        $join->type('LEFT');

                        if ($r->condition()) {
                            $join->on(clone $r->condition());
                        } else {
                            $join->on(new RaiseDbSqlWhere());
                        }

                        $join->on()->operator('AND');

                        $fKey = $r->foreignKey();
                        if ($fKey) {
                            if (!RaiseVariable::isType($fKey, array('RaiseCollection'))) {
                                $fKey = new RaiseCollection($fKey);
                            }
                        } else {
                            $fKey = $model->pkFieldList();
                        }

                        foreach ($model->pkFieldList() as $i => $pk) {
                            $id = RaiseDbSqlIdentifier::tableField($this->table(), $fKey->get($i), $this->connection);
                            $id2 = RaiseDbSqlIdentifier::tableField($alias ? $alias->name() : $model->table(), $pk, $this->connection);
                            $join->on()->subConditions()->add(new RaiseDbSqlWhere('=', $id->__toString(), $id2->__toString()));
                        }

                        $cond->join()->add($join);
                    }
                }
            }
        }

        $c = $this->find($cond, $params);

        if ($c->count() == 1) {
            $cz = $c->get(0);
            if ($this->loadLevel == self::LOAD_NORMAL || $this->loadLevel == self::LOAD_DEEP) {
                foreach ($this->relationships as $k => $r) {
                    if ($r instanceof RaiseDbHasMany) {
                        $model = $this->prepareModel($r->model());

                        if ($model instanceof RaiseDbModel) {

                            $where = $r->condition();
                            if (!$where) {
                                $where = new RaiseDbSqlWhere();
                            }
                            $params = new RaiseCollection();

                            $fKey = $r->foreignKey();
                            if ($fKey) {
                                if (!RaiseVariable::isType($fKey, array('RaiseCollection'))) {
                                    $fKey = new RaiseCollection($fKey);
                                }
                            } else {
                                $fKey = $this->pkFieldList();
                            }

                            foreach ($this->pkFieldList() as $i => $pk) {
                                $id = RaiseDbSqlIdentifier::tableField($model->table(), $fKey->get($i), $this->connection);
                                $where->subConditions()->add(new RaiseDbSqlWhere('=', $id->__toString(), '@' . $this->table() . $pk));
                                $params->add('@' . $this->table() . $pk, $this->$pk);
                            }

                            $conds = new RaiseDbSqlQueryCondition();
                            $conds->where($where);

                            $mc = $model->find($conds, $params);
                            $t = new RaiseCollection();
                            foreach ($mc as $row) {
                                $t->add($row[$model->table()]);
                            }
                            $cz->add((is_string($k) ? $k : ($model instanceof RaiseDbModelBuilder ? $model->table() : get_class($model))), $t);
                        }
                    } elseif ($r instanceof RaiseDbManyToMany) {
                        $model = $this->prepareModel($r->model());

                        if ($model instanceof RaiseDbModel) {
                            $conds = new RaiseDbSqlQueryCondition();

                            $junction = $r->junction();
                            if (is_string($r->junction()) && class_exists($r->junction())) {
                                $m = $r->junction();
                                $junction = new $m($this->connection);
                                unset($m);
                            }

                            $where = $r->condition();
                            if (!$where) {
                                $where = new RaiseDbSqlWhere();
                            }
                            $params = new RaiseCollection();
                            $conds = new RaiseDbSqlQueryCondition();

                            $fKey = $r->foreignKey();
                            if ($fKey) {
                                if (!RaiseVariable::isType($fKey, array('RaiseCollection'))) {
                                    $fKey = new RaiseCollection($fKey);
                                }
                            } else {
                                $fKey = $this->pkFieldList();
                            }

                            if ($fKey->count() != $this->pkFieldList()->count()) {
                                throw new RaiseModelKeyMismatchException('Foreign key in junction table mismatches the primary key in the current model.');
                            }

                            foreach ($this->pkFieldList() as $i => $pk) {
                                $id = RaiseDbSqlIdentifier::tableField(($junction instanceof RaiseDbModel ? $junction->table() : $junction), $fKey->get($i), $this->connection);
                                $where->subConditions()->add(new RaiseDbSqlWhere('=', $id->__toString(), '@' . $this->table() . $pk));
                                $params->add('@' . $this->table() . $pk, $this->$pk);
                            }

                            $tfKey = $r->targetForeignKey();
                            if ($tfKey) {
                                if (!RaiseVariable::isType($tfKey, array('RaiseCollection'))) {
                                    $tfKey = new RaiseCollection($tfKey);
                                }
                            } else {
                                $tfKey = $model->pkFieldList();
                            }

                            if ($tfKey->count() != $model->pkFieldList()->count()) {
                                throw new RaiseModelKeyMismatchException('Foreign key in junction table mismatches the primary key in the target model.');
                            }

                            $mJoin = new RaiseDbSqlJoin($model);

                            foreach ($model->pkFieldList() as $i => $pk) {
                                $id = RaiseDbSqlIdentifier::tableField($model->table(), $pk, $this->connection);
                                $id2 = RaiseDbSqlIdentifier::tableField(($junction instanceof RaiseDbModel ? $junction->table() : $junction), $tfKey->get($i), $this->connection);
                                $mJoin->on()->subConditions()->add(new RaiseDbSqlWhere('=', $id->__toString(), $id2->__toString()));
                            }

                            $conds->join()->add($mJoin);
                            $conds->where($where);

                            $t = $this->table;
                            $name = $junction;
                            if ($name instanceof RaiseDbModel) {
                                $name = $name->table();
                            }
                            $this->table = $name;
                            $order = $this->order;
                            $this->order = null;
                            $mc = $this->find($conds, $params);
                            $this->order = $order;
                            $this->table = $t;
                            $st = new RaiseCollection();
                            foreach ($mc as $z) {
                                $st->add($z[get_class($model)]);
                            }

                            $cz->add((is_string($k) ? $k : ($model instanceof RaiseDbModelBuilder ? $model->table() : get_class($model))), $st);
                        }
                    }
                }
            }
            return $cz;
        } else {
            return null;
        }
    }

    /**
     * Fetch the number of records for this Model
     * @param RaiseDbSqlQueryCondition $condition (optional) The condition to count. If the condition is not specified, the query will count how many records are there in the table.
     * @return integer
     */
    public function count($condition = null) {
        $s = new RaiseDbSqlSelect($this);
        $s->condition($condition);
        $s->field()->add('COUNT(*)');
        $cmd = new RaiseDbCommand($this->connection, $s->__toString());
        $c = $cmd->executeScalar();
        return $c;
    }

    /**
     * Check with the given primary key whether or not the record exists in the persistence
     * @return boolean
     */
    public function exists() {
        $params = new RaiseCollection();
        $conditions = new RaiseDbSqlWhere('AND');
        foreach ($this->pkFieldList() as $pk) {
            if (property_exists($this, $pk)) {
                $params->add('@' . $pk, $this->$pk);
                $conditions->subConditions()->add(new RaiseDbSqlWhere('=', RaiseDbSqlIdentifier::field($pk, $this->connection), '@' . $pk));
            }
        }
        if ($params->count() > 0) {
            $s = new RaiseDbSqlSelect($this);
            $s->field()->add('COUNT(*)');
            $condition = new RaiseDbSqlQueryCondition();
            $condition->where($conditions);
            $s->condition($condition);

            $cmd = new RaiseDbCommand($this->connection, $s);
            $cmd->params($params);
            $c = $cmd->executeScalar();
            return $c == 1;
        } else {
            return false;
        }
    }

    /**
     * Delete the model record from the persistence storage
     * @return boolean Returns TRUE if successful, and FALSE otherwise
     */
    public function delete() {
        $params = new RaiseCollection();
        $pkFields = array();
        foreach ($this->pkFieldList() as $pk) {
            if (property_exists($this, $pk)) {
                $params->add('@' . $pk, $this->$pk);
                $name = RaiseDbSqlIdentifier::field($pk, $this->connection);
                $pkFields[] = $name . ' = @' . $pk;
            }
        }
        if ($params->count() > 0) {
            $name = RaiseDbSqlIdentifier::field($this->table(), $this->connection);
            $cmd = new RaiseDbCommand($this->connection, 'DELETE FROM ' . $name . ' WHERE ' . implode(' AND ', $pkFields));
            $cmd->params($params);
            return $cmd->executeNonQuery();
        } else {
            return false;
        }
    }

    /**
     * Commit changes of the DataAccessObject to the database
     * @return boolean
     * @abstract
     */
    public function commit() {
        $allNull = true;
        if ($this->isCompositePrimaryKey()) {

            if ($this->exists()) {
                // update
                return $this->persistUpdate();
            } else {
                // insert
                $ok = $this->persistInsert();
                return $ok;
            }
        } else {
            if (property_exists($this, $this->primaryKey) && $this->{$this->primaryKey}) {
                $allNull = false;
            }

            if ($allNull) {
                // no PK, so we can assume that PK is auto increment and we will do INSERT instead of UDPATE
                return $this->persistInsert();
            } else {
                // oops, there's a PK, so we'll do UPDATE instead of an INSERT
                return $this->persistUpdate();
            }
        }
    }

    private function hasDateTimeCreated() {
        if ($this->tableColumns()->keyExists(self::$dateTimeCreatedColumn)) {
            $c = $this->tableColumns()->get(self::$dateTimeCreatedColumn);
            if ($c->type() == 'RaiseDateTime') {
                return true;
            }
        }
        return false;
    }

    private function hasDateTimeModified() {
        if ($this->tableColumns()->keyExists(self::$dateTimeModifiedColumn)) {
            $c = $this->tableColumns()->get(self::$dateTimeModifiedColumn);
            if ($c->type() == 'RaiseDateTime') {
                return true;
            }
        }
        return false;
    }

    /**
     * Find the records of this model based on the conditions provided.
     * @param RaiseDbSqlQueryCondition $condition (optional) The conditions for this query
     * @param RaiseCollection|array $params (optional) Parameters of this query
     * @return RaiseCollection The records
     */
    public function find($condition = null, $params = null) {

        if ($condition == null) {
            $condition = new RaiseDbSqlQueryCondition();
        }

        $s = new RaiseDbSqlSelect($this);

        // there are no order in the statement, so let's add the default
        if ($condition->order()->count() == 0) {
            if ($this->order instanceof RaiseDbSqlOrder) {
                $condition->order()->add($this->order);
            } elseif (RaiseVariable::isType($this->order, array('RaiseCollection', 'array'))) {
                foreach ($this->order as $o) {
                    $condition->order()->add($o);
                }
            }
        }

        $mapping = array();

        $columns = $this->tableColumns();
        if ($columns) {
            foreach ($columns as $c) {
                $cIdentifier = RaiseDbSqlIdentifier::tableField($this->table(), $c->name(), $this->connection);
                $ct = $s->field()->count();
                $s->field()->add($ct, $cIdentifier);
                $mapping[$ct] = array($this, $c->name(), false);
            }
        }

        foreach ($condition->join() as $join) {
            $model = $join->model();
            if ($model instanceof RaiseDbSqlIdentifier) {
                $model = $model->name();
            }

            if (is_string($model) && class_exists($model)) {
                $m = $model;
                $model = new $m($this->connection);
                unset($m);
            }
            $columns = $model->tableColumns();
            foreach ($columns as $c) {
                $alias = $join->alias();
                if ($alias instanceof RaiseDbSqlIdentifier) {
                    $alias = $alias->name();
                }
                $cIdentifier = RaiseDbSqlIdentifier::tableField($alias ? $alias : $model->table(), $c->name(), $this->connection);
                $ct = $s->field()->count();
                $s->field()->add($ct, $cIdentifier->__toString());
                $mapping[$ct] = array($model, $c->name(), $alias);
            }
        }

        $s->condition($condition);

        $cmd = new RaiseDbCommand($this->connection, $s->__toString());
        if ($params) {
            $cmd->params()->append($params);
        }
        $table = $cmd->executeFetchArrayTable();
        $result = new RaiseCollection();
        foreach ($table as $row) {
            $rowResult = new RaiseCollection();
            foreach ($mapping as $k => $v) {
                $c = $v[2] ? $v[2] : get_class($v[0]);

                if (!$rowResult->keyExists($c)) {
                    $rowResult->add($c, $v[0]->raiseClone());
                }
                if($row[$k] !== null){
                    if ($v[0]->tableColumns()->get($v[1])->type() == 'RaiseDateTime') {
                        $row[$k] = RaiseDateTime::fromString($row[$k]);
                    }elseif($v[0]->tableColumns()->get($v[1])->type() == RaiseVariable::TYPEINTEGER){
                        $row[$k] = (int)$row[$k];
                    }elseif($v[0]->tableColumns()->get($v[1])->type() == RaiseVariable::TYPEBOOLEAN){
                        $row[$k] = (bool)$row[$k];
                    }
                }
                $rowResult->get($c)->{$v[1]} = $row[$k];
            }
            $result->add($rowResult);
        }

        return $result;
    }

    /**
     * Find the first record of this model based on the conditions provided
     * @param RaiseDbSqlQueryCondition $condition
     * @param RaiseCollection|array $params (optional)
     * @return RaiseCollection
     */
    public function findFirst($condition, $params = null) {
        $condition->limit(1);
        $condition->offset(0);

        return $this->find($condition, $params)->first();
    }

    /**
     * Do an insert operation to add the current model into the storage
     * @return boolean
     */
    private function persistInsert() {
        $params = new RaiseCollection();
        $fields = array();
        $columns = array();
        $tableFields = $this->fieldListWithoutPK();
        $tableFields->append($this->pkFieldList());
        foreach ($tableFields as $t) {
            if ($t !== null && property_exists($this, $t) && ($t != self::$dateTimeCreatedColumn && $t != self::$dateTimeModifiedColumn)) {
                $params->add('@' . $t, $this->$t);
                $name = RaiseDbSqlIdentifier::tableField($this->table(), $t, $this->connection);
                $columns[] = $name;
                $fields[] = '@' . $t;
            }
        }

        if ($this->hasDateTimeCreated() || $this->hasDateTimeModified()) {
            $dtnow = RaiseDateTime::now();
            if ($this->hasDateTimeCreated()) {
                $t = self::$dateTimeCreatedColumn;
                $this->$t = $dtnow;
                $params->add('@' . $this->table() . $t, $dtnow->toISO8601());
                $name = RaiseDbSqlIdentifier::tableField($this->table(), $t, $this->connection);
                $columns[] = $name;
                $fields[] = '@' . $this->table() . $t;
            }

            if ($this->hasDateTimeModified()) {
                $t = self::$dateTimeModifiedColumn;
                $this->$t = $dtnow;
                $params->add('@' . $this->table() . $t, $dtnow->toISO8601());
                $name = RaiseDbSqlIdentifier::tableField($this->table(), $t, $this->connection);
                $columns[] = $name;
                $fields[] = '@' . $this->table() . $t;
            }
        }

        if ($params->count() > 0) {
            $name = RaiseDbSqlIdentifier::field($this->table(), $this->connection);
            $q = 'INSERT INTO ' . $name . ' (' . implode(', ', $columns) . ') VALUES (' . implode(', ', $fields) . ')';
            $cmd = new RaiseDbCommand($this->connection, $q);
            $cmd->params($params);
            $ok = $cmd->executeNonQuery();
            if ($ok && !$this->isCompositePrimaryKey()) {
                $lastInsertId = $this->connection->lastInsertId();
                if ($lastInsertId) {
                    $this->{$this->primaryKey} = $lastInsertId;
                }
            }
            return $ok;
        }
        return false;
    }

    /**
     * Do an update operation to edit the current model in the storage
     * @return boolean
     */
    private function persistUpdate() {
        $params = new RaiseCollection();
        $fields = array();
        foreach ($this->fieldListWithoutPK() as $t) {
            if (property_exists($this, $t) && ($t != self::$dateTimeCreatedColumn && $t != self::$dateTimeModifiedColumn)) {
                $params->add('@' . $this->table() . $t, $this->$t);
                $name = RaiseDbSqlIdentifier::tableField($this->table(), $t, $this->connection);
                $fields[] = $name . ' = @' . $this->table() . $t;
            }
        }

        if ($this->hasDateTimeModified()) {
            $dtnow = RaiseDateTime::now();
            $t = self::$dateTimeModifiedColumn;
            $this->$t = $dtnow;
            $params->add('@' . $this->table() . $t, $dtnow->toISO8601());
            $name = RaiseDbSqlIdentifier::tableField($this->table(), $t, $this->connection);
            $fields[] = $name . ' = @' . $this->table() . $t;
        }

        if ($params->count() > 0) {
            $pkFields = array();
            foreach ($this->pkFieldList() as $pk) {
                if (property_exists($this, $pk)) {
                    $params->add('@' . $this->table() . $pk, $this->$pk);
                    $name = RaiseDbSqlIdentifier::tableField($this->table(), $pk, $this->connection);
                    $pkFields[] = $name . ' = @' . $this->table() . $pk;
                }
            }
            if (count($pkFields) > 0) {
                $name = RaiseDbSqlIdentifier::field($this->table(), $this->connection);
                $q = 'UPDATE ' . $name . ' SET ' . implode(', ', $fields) . ' WHERE ' . implode(', ', $pkFields);
                $cmd = new RaiseDbCommand($this->connection, $q);
                $cmd->params($params);
                return $cmd->executeNonQuery();
            }
        }
        return false;
    }

    /**
     * Check if a field is part of the composite primary key or is the primary key
     * @param RaiseDbTableColumn $field The name of the field to check
     * @return boolean
     */
    private function isFieldPK($field) {
        $fieldName = $field->name();
        return ($this->isCompositePrimaryKey() && in_array($fieldName, $this->primaryKey)) || (!$this->isCompositePrimaryKey() && $this->primaryKey == $fieldName);
    }

    /**
     * Get the collection of non-Primary Key fields of the model
     * @return RaiseCollection
     */
    private function fieldListWithoutPK() {
        $fields = new RaiseCollection();
        foreach ($this->tableColumns() as $t) {
            if (!$this->isFieldPK($t)) {
                $fields->add($t->name());
            }
        }
        return $fields;
    }

    /**
     * Return the table columns for this connection / model
     * @return RaiseTypedCollection
     */
    protected function tableColumns() {
        return self::$tableModel->get($this->connection->hashCode() . $this->table());
    }

    /**
     * Get the collection of Primary Key fields of the model
     * @return RaiseCollection
     */
    private function pkFieldList() {
        $fields = new RaiseCollection();
        foreach ($this->tableColumns() as $t) {
            if ($this->isFieldPK($t)) {
                $fields->add($t->name());
            }
        }
        return $fields;
    }

    /**
     * Get or set the connection in which this object should use
     * @param RaiseDbConnection $conn (optional) The new connection this object should use
     * @return RaiseDbConnection
     */
    public function connection($c = null) {
        if (func_num_args() == 1) {
            if (!($c instanceof RaiseDbConnection)) {
                throw new RaiseInvalidArgumentException('RaiseDbModel::connection() expecting first parameter to be of type RaiseDbConnection. ' . RaiseVariable::typeInfo($c) . ' given instead.');
            }
            $this->connection = $c;
        }
        return $this->connection;
    }

}
